// ***************************************************************************************
// Copyright (c) 2023-2025 Peng Cheng Laboratory
// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences
// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip
//
// iEDA is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
//
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
//
// See the Mulan PSL v2 for more details.
// ***************************************************************************************
/**
 * @File Name: contest_process.cpp
 * @Brief :
 * @Author : Yell (12112088@qq.com)
 * @Version : 1.0
 * @Creat Date : 2023-09-15
 *
 */

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "contest_process.h"

#include "contest_preprocess.h"



// #include "PLAPI.hh"
// #include "PlacerDB.hh"

// #define NOOPT
#define OPT

namespace ieda_contest {
uint64_t ContestProcess::_init_instance_area = 0;
uint64_t ContestProcess::_max_instance_area = 0;
struct CmpContestNet
{
  bool operator()(ContestNet& a, ContestNet& b)
  {
    auto aabb_a = getAABB(a);
    auto aabb_b = getAABB(b);
    auto area_a = (aabb_a.get_second().get_x() - aabb_a.get_first().get_x()) * (aabb_a.get_second().get_y() - aabb_a.get_first().get_y());
    auto area_b = (aabb_b.get_second().get_x() - aabb_b.get_first().get_x()) * (aabb_b.get_second().get_y() - aabb_b.get_first().get_y());
    if (area_a == area_b) {
      auto length_a
          = std::max(aabb_a.get_second().get_x() - aabb_a.get_first().get_x(), aabb_a.get_second().get_y() - aabb_a.get_first().get_y());
      auto length_b
          = std::max(aabb_b.get_second().get_x() - aabb_b.get_first().get_x(), aabb_b.get_second().get_y() - aabb_b.get_first().get_y());
      return length_a < length_b;
    }
    return area_a > area_b;
  }

  ContestSegment getAABB(ContestNet& n)
  {
    auto& pins = n.get_pin_list();

    ContestSegment aabb(pins[0].get_grid_coord(), pins[0].get_grid_coord());
    for (ContestPin& pin : pins) {
      if (pin.get_grid_coord().get_x() < aabb.get_first().get_x()) {
        aabb.get_first().set_x(pin.get_grid_coord().get_x());
      }
      if (pin.get_grid_coord().get_y() < aabb.get_first().get_y()) {
        aabb.get_first().set_y(pin.get_grid_coord().get_y());
      }
      if (pin.get_grid_coord().get_x() > aabb.get_second().get_x()) {
        aabb.get_second().set_x(pin.get_grid_coord().get_x());
      }
      if (pin.get_grid_coord().get_y() > aabb.get_second().get_y()) {
        aabb.get_second().set_y(pin.get_grid_coord().get_y());
      }
    }
    return aabb;
  }
};

ContestProcess::ContestProcess(ContestDataManager* data_manager)
{
  _data_manager = data_manager;

  Flute::readLUT();
}


/**
 * @brief Perform incremental timing optimization
 * 
 * Optimization Flow:
 *  Step 1. Timing-Driven placement
 *  Step 2. Gate Sizing
 *  Step 3. Timing-Driven Overlap Reducation
 *  Step 4. Routing, Rip-up and Rerouting
 */
void ContestProcess::doTimingOpt() {
  ieda::Stats _to_status;
  ContestEvaluation contest_eval(_data_manager);

/*
  Step 1. Timing-Driven placement
*/
  timingDrivenPlacement();

/*
  Step 2. Gate Sizing
*/
  gateSizing();

/*
  Step 3. Timing-Driven Overlap Reducation
*/
  timingDrivenOverlapReducation();

/*
  Step 4. Routing, Rip-up and Rerouting
*/
  routing();

  double time_delta = _to_status.elapsedRunTime();
  std::cout << "============= Total Flow Run Time Elapsed: " << time_delta << "s\n";
}

/**
 * @brief Step 1. Timing-Driven placement
 * 
 */
void ContestProcess::timingDrivenPlacement() {
  _qtdp = new QuadraticTDP(_data_manager->get_database());
  DrivenStrengthCellMovement* dscm = new 
                              DrivenStrengthCellMovement(_data_manager->get_database());
  HistogramCompression* hs = new HistogramCompression(_data_manager);

  std::cout << "Cell move start====\n";
  ieda::Stats cell_movement_status;
/* 
  Main Function 
*/
  hs->runHistogramCompressionOpt1();
  dscm->runDrivenStrengthAwareOpt(_qtdp);
  double cell_movement_time_delta = cell_movement_status.elapsedRunTime();
  std::cout << "Cell move end====\n";
  std::cout << "=============FUNC : Cell Movement Techniques Total Run Time Elapsed: " 
            << cell_movement_time_delta << "s\n";

  // write back to idb
  auto inst_list = _data_manager->get_database()->get_ContestInstance_pvec();
  auto idb_inst_map = _data_manager->get_database()->get_Idbinstace_map();

  for (auto pgp_inst : inst_list) {
    auto idb_inst = idb_inst_map[pgp_inst];
    idb_inst->set_coodinate(pgp_inst->get_lower_point().get_x(), pgp_inst->get_lower_point().get_y());
    _init_instance_area += idb_inst->get_cell_master()->get_height() * idb_inst->get_cell_master()->get_width();
  }
  _max_instance_area = (1 + _data_manager->get_gcell_area_constraint()) * _init_instance_area; 
  std::cout << "_init_instance_area : " << _init_instance_area << " _max_instance_area " << _max_instance_area << std::endl;
}

/**
 * @brief Step 2. Gate Sizing
 * 
 */
void ContestProcess::gateSizing() {
  auto* idb_builder = dmInst->get_idb_builder();
  auto sta = _data_manager->get_database()->get_timing_engine();
  ieda::Stats gate_sizing_status;
  GateSizing* gate_size = new GateSizing(idb_builder, sta);
/* 
  Main Function 
*/
  gate_size->doGateSizing(_to_status.elapsedRunTime(), _init_instance_area, 
                          _max_instance_area);
  gate_size->doDownSize(_init_instance_area, _max_instance_area);
  gate_size->doGateSizing(_to_status.elapsedRunTime(), _init_instance_area, 
                          _max_instance_area);
  double gate_sizing_time_delta = gate_sizing_status.elapsedRunTime();
  std::cout << "=============FUNC : Gate Sizing Technique Total Run Time Elapsed: " 
            << gate_sizing_time_delta << "s\n";
  // printWnsAndTns();

  // 把gate sizing的结果写回到Contest数据
  auto inst_list = _data_manager->get_database()->get_ContestInstance_pvec();
  auto idb_inst_map = _data_manager->get_database()->get_Idbinstace_map();
  for (auto pgp_inst : inst_list) {
    auto idb_inst = idb_inst_map[pgp_inst];
    pgp_inst->set_height(idb_inst->get_cell_master()->get_height());
    pgp_inst->set_width(idb_inst->get_cell_master()->get_width());
    pgp_inst->set_area(idb_inst->get_cell_master()->get_height() * idb_inst->get_cell_master()->get_width());
  }
}

/**
 * @brief Step 3. Timing-Driven Overlap Reducation
 * 
 */
void ContestProcess::timingDrivenOverlapReducation() {
  ieda::Stats abu_education;
  AbuReduction* abu_reduction = new AbuReduction(_data_manager, _qtdp);
/* 
  Main Function 
*/
  abu_reduction->runAbuReduction();
  double abu_education_time_delta = abu_education.elapsedRunTime();
  std::cout << "=============FUNC : Abu Reducation Total Run Time Elapsed: " 
            << abu_education_time_delta << "s\n";

  // write back to idb
  auto inst_list = _data_manager->get_database()->get_ContestInstance_pvec();
  auto idb_inst_map = _data_manager->get_database()->get_Idbinstace_map();
  for (auto pgp_inst : inst_list) {
    auto idb_inst = idb_inst_map[pgp_inst];
    // idb_inst->set_status_placed();
    idb_inst->set_coodinate(pgp_inst->get_lower_point().get_x(), pgp_inst->get_lower_point().get_y());
  }
}

/**
 * @brief Step 4. Routing, Rip-up and Rerouting
 * 
 */
void ContestProcess::routing() {
  // Routing
  auto make_route = new MakeRoute(_data_manager);
  std::vector<ContestNet>& contest_net_list = _data_manager->get_database()->get_net_list();
  contest_net_list.clear();
  auto idb_net_list = _data_manager->get_idb_design()->get_net_list()->get_net_list();
  int net_id = -1;
  for (auto idb_net : idb_net_list) {
    net_id++;
    auto contest_net = makeContestNet(idb_net, net_id);
    contest_net_list.emplace_back(contest_net);
  }

  std::sort(contest_net_list.begin(), contest_net_list.end(), CmpContestNet());
  ieda::Stats route_state;
  int net_num = contest_net_list.size();
  int percent = 0;
  double route_time_delta = 0;
  double time_last = 0;
  for (int i = 0; i < net_num; i++) {
    auto contest_net = contest_net_list[i];
    contest_net.set_id(i);
    make_route->doRoute(&contest_net);
    contest_net_list[i] = contest_net;
    route_time_delta = route_state.elapsedRunTime();
    if (route_time_delta - time_last > 300) {  // ((i + 1) % batch_size == 0) {
      percent = static_cast<int>((i + 1.0) / net_num * 10000);
      std::cout << " " << percent / 100 << "." << std::setw(2) << std::setfill('0') << percent % 100 << "% ";

      std::cout << "Routed " << (i + 1) << " nets, ";

      std::cout << "Time: " << route_time_delta << "s " << std::endl;
      time_last = route_time_delta;
    }
  }

  // rip-up and reroute
  ieda::Stats overflow_education;
  ripupAndReroute(make_route);
  double overflow_education_time_delta = overflow_education.elapsedRunTime();
  std::cout << "=============FUNC : Overflow Reducation Total Run Time Elapsed: " 
            << overflow_education_time_delta << "s\n";
}

ContestNet ContestProcess::makeContestNet(idb::IdbNet* idb_net, int net_id)
{
  ContestNet contest_net;
  contest_net.set_net_name(idb_net->get_net_name());
  contest_net.set_id(net_id);
  std::vector<ContestPin>& pin_list = contest_net.get_pin_list();
  for (idb::IdbPin* idb_pin : idb_net->get_instance_pin_list()->get_pin_list()) {
    ContestCoord coord(idb_pin->get_instance()->get_bounding_box()->get_middle_point_x(),
                       idb_pin->get_instance()->get_bounding_box()->get_middle_point_y(), 0);

    ContestPin contest_pin;
    contest_pin.set_real_coord(coord);
    contest_pin.set_grid_coord(getGCellCoord(coord));
    contest_pin.get_contained_instance_list().push_back(idb_pin->get_instance()->get_name());
    pin_list.push_back(contest_pin);
  }
  if (idb_net->get_io_pin() != nullptr) {
    idb::IdbPin* io_pin = idb_net->get_io_pin();
    ContestCoord coord(io_pin->get_bounding_box()->get_middle_point_x(), io_pin->get_bounding_box()->get_middle_point_y(), 0);

    ContestPin contest_pin;
    contest_pin.set_real_coord(coord);
    contest_pin.set_grid_coord(getGCellCoord(coord));
    contest_pin.get_contained_instance_list().push_back("io");
    pin_list.push_back(contest_pin);
  }
  return contest_net;
}

void ContestProcess::printWnsAndTns()
{
  ista::TimingEngine* timing_engine = _data_manager->get_database()->get_timing_engine();
  const char* master_clock_name = "CLK_osc_in";
  double wns = timing_engine->get_ista()->getWNS(master_clock_name, AnalysisMode::kMax);
  double tns = timing_engine->get_ista()->getTNS(master_clock_name, AnalysisMode::kMax);

  std::cout << "wns: " << wns << std::endl;
  std::cout << "tns: " << tns << std::endl;
}

ContestCoord ContestProcess::getGCellCoord(const ContestCoord& coord)
{
  int single_gcell_x_span = _data_manager->get_database()->get_single_gcell_x_span();
  int single_gcell_y_span = _data_manager->get_database()->get_single_gcell_y_span();

  ContestCoord gcell_coord;
  gcell_coord.set_x((coord.get_x() > 0 ? coord.get_x() - 1 : coord.get_x()) / single_gcell_x_span);
  gcell_coord.set_y((coord.get_y() > 0 ? coord.get_y() - 1 : coord.get_y()) / single_gcell_y_span);
  gcell_coord.set_layer_idx(coord.get_layer_idx());
  return gcell_coord;
}

/**
 * @brief Rip-up and Rerouting
 * 
 * @param make_route 
 */
void ContestProcess::ripupAndReroute(MakeRoute* make_route)
{
  std::vector<ContestNet>& contest_net_list = _data_manager->get_database()->get_net_list();
  int iter = 0;
  while (iter < 5) {
    iter++;
    CheckOverflow contest_eval1(_data_manager);
    contest_eval1.overflowCheckPassed();
    auto overflow_gcell = contest_eval1.get_overflow_gcell();
    // auto overfloe_net_id = contest_eval1.get_overflow_net_id();
    auto gcell_used = contest_eval1.get_gcell_used();

    std::map<ContestCoord, std::vector<netID>> gcell_nets = contest_eval1.get_gcell_nets();
    std::map<netID, std::set<ContestCoord>> net_gcells = contest_eval1.get_net_gcells();

    std::set<netID> reroute_nets_set;
    for (auto vio_gcell : overflow_gcell) {
      auto reroute_nets = gcell_nets[vio_gcell];                          // reroute nets
      reroute_nets_set.insert(reroute_nets.begin(), reroute_nets.end());  // TODO:
      // update gcell usage info
      for (auto reroute_id : reroute_nets) {
        auto gcells = net_gcells[reroute_id];
        for (auto gcell : gcells) {
          gcell_used[gcell] -= 1;
        }
      }
    }

    make_route->updateGridMapInfo(gcell_used);

    for (auto net_id : reroute_nets_set) {
      auto reroute_net = contest_net_list[net_id];
      make_route->doRoute(&reroute_net);
      contest_net_list[net_id] = reroute_net;
    }

    overflow_gcell.clear();
    // contest_eval1.overflowCheckPassed(_data_manager);
    // overflow_gcell = contest_eval1.get_overflow_gcell();
    CheckOverflow contest_eval2(_data_manager);
    contest_eval2.overflowCheckPassed(_data_manager);
    overflow_gcell = contest_eval2.get_overflow_gcell();
    if (overflow_gcell.empty()) {
      break;
    }
  }
}

}  // namespace ieda_contest
